package main

import "fmt"

/*
Go语言中没有“类”的概念，也不支持“类”的继承等面向对象的概念。Go语言中通过结构体的内嵌再配合接口比面向对象具有更高的扩展性和灵活性。

Go语言中的基础数据类型可以表示一些事物的基本属性，但是当我们想表达一个事物的全部或部分属性时，这时候再用单一的基本数据类型明显就无法满足需求了，
Go语言提供了一种自定义数据类型，可以封装多个基本数据类型，这种数据类型叫结构体，可以通过struct来定义自己的类型了。

使用type和struct关键字来定义结构体，具体代码格式如下：
type 类型名 struct {
    字段名 字段类型
    字段名 字段类型
    …
}
	类型名：标识自定义结构体的名称，在同一个包内不能重复。
	字段名：表示结构体字段名。结构体中的字段名必须唯一。
	字段类型：表示结构体字段的具体类型。

结构体实例化三种方式：
1).基本实例化：var p person
2).创建指针型结构体 var p = new(person)
3).取结构体的地址实例化 var p = &person{}

3 结构体初始化
	 3.1 使用键值对初始化
	 3.2 使用值的列表初始化 使用这种格式初始化时，需要注意：
			必须初始化结构体的所有字段。
			初始值的填充顺序必须与字段在结构体中的声明顺序一致。
			该方式不能和键值初始化方式混用。


4. Go语言的结构体没有构造函数，需要自己实现。

5. 方法和接收者
Go语言中的方法（Method）是一种作用于特定类型变量的函数。这种特定类型变量叫做接收者（Receiver）。接收者的概念就类似于其他语言中的this或者 self。
方法的定义格式如下：
func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
    函数体
}
接收者变量：接收者中的参数变量名在命名时，官方建议使用接收者类型名称首字母的小写，而不是self、this之类的命名。例如，Person类型的接收者变量应该命名为 p，Connector类型的接收者变量应该命名为c等。
接收者类型：接收者类型和参数类似，可以是指针类型和非指针类型。
方法名、参数列表、返回参数：具体格式与函数定义相同。

方法与函数的区别是，函数不属于任何类型，方法属于特定的类型。

5.1 指针类型的接收者
指针类型的接收者由一个结构体的指针组成，由于指针的特性，调用方法时修改接收者指针的任意成员变量，在方法结束后，修改都是有效的。这种方式就十分接近于其他语言中面向对象中的this或者self。

5.2 值类型的接收者
当方法作用于值类型接收者时，Go语言会在代码运行时将接收者的值复制一份。在值类型接收者的方法中可以获取接收者的成员值，但修改操作只是针对副本，无法修改接收者变量本身。

什么时候应该使用指针类型接收者
1). 需要修改接收者中的值
2). 接收者是拷贝代价比较大的大对象
3). 保证一致性，如果有某个方法使用了指针接收者，那么其他的方法也应该使用指针接收者。

6. 任意类型添加方法
在Go语言中，接收者的类型可以是任何类型，不仅仅是结构体，任何类型都可以拥有方法。
举个例子，我们基于内置的int类型使用type关键字可以定义新的自定义类型，然后为我们的自定义类型添加方法。

注意事项： 非本地类型不能定义方法，也就是说我们不能给别的包的类型定义方法。

7. 结构体的匿名字段
结构体允许其成员字段在声明时没有字段名而只有类型，这种没有名字的字段就称为匿名字段。
注意：这里匿名字段的说法并不代表没有字段名，而是默认会采用类型名作为字段名，结构体要求字段名称必须唯一，因此一个结构体中同种类型的匿名字段只能有一个。

8. 嵌套结构体
一个结构体中可以嵌套包含另一个结构体或结构体指针。

9. 结构体的“继承”
通过嵌套匿名结构体实现继承；如：type Dog struct{ *Animal } ； 注意嵌套的是结构体指针。

10. 结构体字段的可见性: 结构体中字段大写开头表示可公开访问，小写表示私有（仅在定义当前结构体的包中可访问）。

*/

type student struct {
	name string
	age  int
}

//1. 使用type关键字来定义自定义类型。
// 1.1 自定义类型
type NewInt int

// 1.2 类型别名
type MyInt = int

var a NewInt
var b MyInt

// 2. 结构体定义
type person struct {
	name string
	age  int
	city string
}

// 3. 结构体初始化

// 面试题
func mianshiti() {
	sm := make(map[string]*student)
	stus := []student{
		{name: "xiaoming", age: 18},
		{name: "xiaogang", age: 19},
		{name: "xiaoqiang", age: 20},
	}
	for _, stu := range stus {
		sm[stu.name] = &stu
	}
	for k, v := range sm {
		fmt.Println(k, " => ", v.name)
	}
}

// 4. 构造函数
func newPerson(name string, age int, city string) *person {
	return &person{
		name,
		age,
		city,
	}
}

// 5. 方法和接收者
func (p person) Dream() {
	fmt.Println(p.age)
	p.age = 30
	fmt.Println(p.age)
	fmt.Printf("%s的梦想是学好Go语言！\n", p.name)
}

// 指针类型的接收者
func (p *person) setAge(age int) {
	p.age = age
}

// 结构体嵌套 和继承
type Address struct {
	city string
	area string
}

func (a *Address) setCity(c string) {
	fmt.Println(a)
	a.city = c
	fmt.Println(a)
}

type UserAddr struct {
	name    string
	age     int
	city    string
	Address Address
}

type UserAddrTwo struct {
	name string
	age  int
	city string
	*Address
}

func (u *UserAddrTwo) uname() {
	fmt.Println(u.name)
}

func main() {
	// 类型定义和类型别名的区别:a的类型是main.NewInt，表示main包下定义的NewInt类型。
	// b的类型是int。MyInt类型只会在代码中存在，编译完成时并不会有MyInt类型。
	fmt.Printf("%T %v \n", a, a)
	fmt.Printf("%T %v \n", b, b)

	// 2.1 结构体实例化 赋值
	var p person
	fmt.Printf("基本实例化 p: %#v \n", p)
	p.name = "xiaoming"
	p.age = 28
	p.city = "beijing"
	fmt.Println(p)
	fmt.Printf("p: %#v \n", p)

	// 2.2 匿名结构体
	var u struct {
		name string
		age  int8
	}
	u.name = "xiaogang"
	u.age = 29
	fmt.Printf("u: %#v \n", u)

	// 2.3 指针类型结构体
	// 使用new关键字对结构体进行实例化，得到的是结构体的地址。
	var p2 = new(person)
	fmt.Println(p2, *p2)
	fmt.Printf("指针类型结构体 p2: %#v \n", p2)
	p2.name = "xiaoqiang"
	p2.age = 19
	fmt.Printf("p2: %#v \n", p2)

	// 2.4 取结构体的地址实例化 使用&对结构体进行取地址操作相当于对该结构体类型进行了一次new实例化操作。
	var p3 = &person{}
	fmt.Printf("取结构体的地址实例化 p3: %#v \n", p3)
	p3.name = "xiaofang" // p3.name = "xiaofang"其实在底层是(*p3).name = "xiaofang"，这是Go语言帮我们实现的语法糖。
	p3.age = 23
	p3.city = "shanghai"
	fmt.Printf("p3: %#v \n", p3)

	//面试题
	mianshiti()

	//调用构造函数
	p4 := newPerson("xiaofang", 28, "shanghai")
	fmt.Println(p4)
	p4.Dream()
	fmt.Println(p4.age)
	p4.setAge(39)
	fmt.Println(p4.age)

	// 结构体嵌套
	user1 := UserAddr{
		name: "xiaoyuan",
		age:  28,
		city: "beijing",
		Address: Address{
			city: "shanghai",
			area: "baoshanqv",
		},
	}
	fmt.Println(user1)

	// 结构体的“继承”
	user2 := &UserAddrTwo{
		name: "xiaoliu",
		age:  28,
		city: "beijing",
		Address: &Address{
			city: "nanjing",
			area: "baoshanqv",
		},
	}
	user2.uname()
	user2.setCity("hangzhou")

}
